﻿using System;

namespace CNG
{
    /// <summary>
    /// only works from vista sp1  
    /// </summary>
    public abstract class RandomNextGen : Random
    {

        RNGCng random;

        /// <param name="algorithm">http://msdn.microsoft.com/en-us/library/aa375534(VS.85).aspx</param>
        public RandomNextGen(string algorithm)
        {
            var _ver = Environment.OSVersion.Version;
            if (_ver.Major < 6) throw new InvalidOperationException();
            if (_ver.Build <= 6000) throw new InvalidOperationException();

            random = new RNGCng(algorithm);
        }

        /// <summary>
        /// get next random int
        /// </summary>
        /// <returns></returns>
        public override int Next()
        {
            return BitConverter.ToInt32(GenerateKey(sizeof(UInt32)), 0) & int.MaxValue;
        }

        [CLSCompliant(false)]
        public UInt64 NextUInt64()
        {
            return BitConverter.ToUInt64(GenerateKey(sizeof(UInt64)), 0) & UInt64.MaxValue;
        }

        public Int64 NextInt64()
        {
            return BitConverter.ToInt64(GenerateKey(sizeof(UInt64)), 0) & Int64.MaxValue;
        }

        public int Next(bool AllowNegetive)
        {
            if (AllowNegetive)
                return Next();
            return Math.Abs(Next());
        }

        public override void NextBytes(byte[] buffer)
        {
            random.GetBytes(buffer);
        }

        public void NextNonZeroBytes(byte[] buffer)
        {
            random.GetNonZeroBytes(buffer);
        }

        public byte[] GenerateKey(int size)
        {
            return random.GenerateKey(size);
        }

        public override int Next(int maxValue)
        {
            if (maxValue < 0) throw new ArgumentOutOfRangeException("maxValue");
            return Next(0, maxValue);
        }

        public override int Next(int minValue, int maxValue)
        {
            if (minValue > maxValue) throw new ArgumentOutOfRangeException("minValue");
            if (minValue == maxValue) return minValue;
            Int64 diff = maxValue - minValue;

            while (true)
            {
                UInt32 rand = BitConverter.ToUInt32(GenerateKey(sizeof(UInt32)), 0);

                Int64 max = (1 + (Int64)UInt32.MaxValue);
                Int64 remainder = max % diff;
                if (rand < max - remainder)
                {
                    return (Int32)(minValue + (rand % diff));
                }
            }

        }

        public override double NextDouble()
        {
            return BitConverter.ToUInt32(GenerateKey(sizeof(UInt32)), 0) / (1.0 + UInt32.MaxValue);
        }

        public double NextDouble(bool AllowNegetive)
        {
            if (AllowNegetive)
                return NextDouble();
            return Math.Abs(NextDouble());
        }

        protected override double Sample()
        {
            return 0;//should never get here
        }
    }
}
